package top.malaoshi.webmvc;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import top.malaoshi.sprout.ioc.DefaultSingletonBeanRegistry;
import top.malaoshi.webmvc.info.MethodInfo;

import java.lang.reflect.Method;
import java.util.*;

/**
 * 解析 @ExceptionHandler 注解修饰的方法
 */
public class ExceptionHandlerMethodResolver {

    private DefaultSingletonBeanRegistry registry = DefaultSingletonBeanRegistry.me();

    /**
     * ExceptionHandler 修饰的方法
      */
    private List<MethodInfo> methodInfos = new ArrayList<>();

    private ResponseHandler jsonResponseHandler = new JsonResponseHandler();
    private ResponseHandler textResponseHandler = new TextResponseHandler();
    /**
     * 处理方法和路径的映射
     */
    public void resolve() throws Exception {


        registry.forEachSingletonObjects((beanName,instance)->{
            Class<?> clazz = instance.getClass();
            //处理 controller、ControllerAdvice 修饰的类
            if (clazz.isAnnotationPresent(Controller.class) || clazz.isAnnotationPresent(ControllerAdvice.class)) {
                // 判断类上是否有 ResponseBody
                boolean hasResponseBodyOfClass = clazz.isAnnotationPresent(ResponseBody.class);

                Method[] methods = clazz.getMethods();
                for(Method method : methods){

                    ExceptionHandler annotation = method.getAnnotation(ExceptionHandler.class);
                    // 有 ExceptionHandler 注解
                    if(annotation!=null){
                        // 判断方法上是否有 ResponseBody
                        boolean hasResponseBodyOfMethods = method.isAnnotationPresent(ResponseBody.class);

                        ResponseHandler responseHandler = null;
                        Class<?> returnType = method.getReturnType();
                        // 返回 Json
                        if(hasResponseBodyOfClass || hasResponseBodyOfMethods){
                            responseHandler = jsonResponseHandler;
                        }else if(returnType == null){

                        }else if(returnType == String.class){
                            responseHandler = textResponseHandler;
                        }
                        MethodInfo mi = MethodInfo.of(beanName, method, responseHandler,null);

//                        List<MethodInfo> methodsOfMap = methodInfoMap.computeIfAbsent(beanName, k -> new ArrayList<>());

                        // 解析该方法的形参
                        ExceptionHandlerMethodArgumentResolver.handler(method,mi);

//                        methodsOfMap.add(mi);
                        methodInfos.add(mi);

                    }
                }

            }else if (clazz.isAnnotationPresent(RestController.class) || clazz.isAnnotationPresent(RestControllerAdvice.class)) {

                //处理 RestController、RestControllerAdvice 修饰的类
                Method[] methods = clazz.getMethods();
                for(Method method : methods){

                    ExceptionHandler annotation = method.getAnnotation(ExceptionHandler.class);
                    // 有 ExceptionHandler 注解
                    if(annotation!=null){
                        MethodInfo mi = MethodInfo.of(beanName, method, jsonResponseHandler,null);

//                        List<MethodInfo> methodsOfMap = methodInfoMap.computeIfAbsent(beanName, k -> new ArrayList<>());

                        // 解析该方法的形参
                        ExceptionHandlerMethodArgumentResolver.handler(method,mi);

//                        methodsOfMap.add(mi);
                        methodInfos.add(mi);

                    }
                }

            }

        });
        sort();
    }

    private void sort(){
        Collections.sort(methodInfos,new MethodInfosComparator());
    }
    class MethodInfosComparator implements Comparator<MethodInfo> {
        /**
         * 获取方法的 @ExceptionHandler注解的异常，子类异常在前，父类异常在后
         * 如果异常是 Throwable就排到最后
         * Exception 排到倒数第1
         * RuntimeException 排到倒数第二
         *
         * 父类异常排到后面
         * @param o1 后一个元素
         * @param o2 前一个元素
         * @return
         */
        @Override
        public int compare(MethodInfo o1, MethodInfo o2) {
            int ret=0;
            ExceptionHandler exceptionHandler1 = o1.getMethod().getAnnotation(ExceptionHandler.class);
            ExceptionHandler exceptionHandler2 = o2.getMethod().getAnnotation(ExceptionHandler.class);

            Class<? extends Throwable>[] clazzs1 = exceptionHandler1.value();
            Class<? extends Throwable>[] clazzs2 = exceptionHandler2.value();

            for(Class item1 : clazzs1){

                for(Class item2 : clazzs2){
                    // item2 是父类
                    if(item2.isAssignableFrom(item1)){
                        return -1;
                    }else if(item1.isAssignableFrom(item2)){ // item1 是父类
                        return 1;
                    }else{
                        return 0;
                    }
                }

            }


            return ret;
        }
    }
    /**
     * 获取匹配的 MethodInfo
     * @param beanName 传入bean名字
     * @param e 传入异常
     * @return
     */
    public List<MethodInfo> getByBeanNameAndException(String beanName, Exception e){
        List<MethodInfo> ret = new ArrayList<>();
        for(MethodInfo methodInfo : methodInfos){
            if(!beanName.equals(methodInfo.getBeanName())){
                continue;
            }
            Method method = methodInfo.getMethod();
            ExceptionHandler annotation = method.getAnnotation(ExceptionHandler.class);
            for(Class exception : annotation.value()){
                if(exception.isInstance(e)){
                    ret.add(methodInfo);
                    break;
                }
            }
        }
        return ret;
    }

    /**
     * 获取匹配的 MethodInfo
     * @param e 传入异常
     * @return
     */
    public List<MethodInfo> getByControllerAdviceAndException(Exception e){
        List<MethodInfo> ret = new ArrayList<>();

        for(MethodInfo methodInfo : methodInfos){

            Object bean = registry.getBeanByName(methodInfo.getBeanName());
            Class<?> clazz = bean.getClass();

            boolean hasControllerAdviceAnn = clazz.isAnnotationPresent(ControllerAdvice.class);
            boolean hasRestControllerAdviceAnn = clazz.isAnnotationPresent(RestControllerAdvice.class);

            if( !hasControllerAdviceAnn && !hasRestControllerAdviceAnn){
                return null;
            }

            Method method = methodInfo.getMethod();
            ExceptionHandler annotation = method.getAnnotation(ExceptionHandler.class);
            for(Class exception : annotation.value()){
                if(exception.isInstance(e)){
                    ret.add(methodInfo);
                    break;
                }
            }
        }



        return ret;
    }
}
